# -*- coding: utf-8 -*-
# @Time    : 2024/6/6 11:21
# @Author  : yujiahao
# @File    : 07_list.py
# @description:Python中的列表

"""
Python、Java 和 Scala 中列表的介绍、使用方法、使用场景和注意事项

一、Python
1. 列表（List）
    - 介绍：列表是一种有序的可变集合，支持重复元素。列表在 Python 中是最常用的数据结构之一，
            可以动态调整大小，允许包含不同类型的元素。

            列表相比于字符串，不仅可以储存不同的数据类型，而且可以储存大量数据，
            32位python的限制是 536870912 个元素,
            64位python的限制是 1152921504606846975 个元素。而且列表是有序的，有索引值，可切片，方便取值。
    - 使用方法：
        # 创建列表
        my_list = [1, 2, 3, 4, 5]

        # 访问元素
        first_element = my_list[0]

        # 修改元素
        my_list[0] = 10

        # 添加元素
        my_list.append(6)

        # 删除元素
        my_list.remove(3)

        # 遍历列表
        for element in my_list:
            print(element)

        # 列表切片
        sub_list = my_list[1:3]

    - 使用场景：需要存储有序的、可变的数据集合时，如存储学生名单、待办事项、动态数据等。
    - 注意事项：列表是动态数组，支持动态扩展，但频繁的插入和删除操作可能会影响性能，特别是在大规模数据处理时。

二、Java
1. 列表（List）
    - 介绍：List 是一种有序的可变集合，支持重复元素。Java 中的 List 接口有多种实现，如 ArrayList、LinkedList 等，
            其中 ArrayList 是最常用的实现，基于动态数组。
    - 使用方法：
        import java.util.ArrayList;
        import java.util.List;

        // 创建列表
        List<Integer> myList = new ArrayList<>();
        myList.add(1);
        myList.add(2);
        myList.add(3);

        // 访问元素
        int firstElement = myList.get(0);

        // 修改元素
        myList.set(0, 10);

        // 删除元素
        myList.remove(Integer.valueOf(3));

        // 遍历列表
        for (int element : myList) {
            System.out.println(element);
        }

        // 列表切片（子列表）
        List<Integer> subList = myList.subList(1, 3);

    - 使用场景：需要存储有序的、可变的数据集合时，如存储员工名单、购物车商品、动态数据等。
    - 注意事项：ArrayList 是基于数组实现的，支持快速随机访问，但插入和删除操作可能较慢。对于频繁插入和删除操作，建议使用 LinkedList。

三、Scala
1. 列表（List）
    - 介绍：List 是一种有序的不可变集合，支持重复元素。Scala 中的 List 是不可变的，所有操作都会返回一个新的列表。
            这使得 List 在并发环境中非常安全。
    - 使用方法：
        // 创建列表
        val myList = List(1, 2, 3, 4, 5)

        // 访问元素
        val firstElement = myList(0)

        // 遍历列表
        for (element <- myList) {
            println(element)
        }

        // 列表切片
        val subList = myList.slice(1, 3)

    - 使用场景：需要存储有序的、不可变的数据集合时，如存储数据流、函数的参数、不可变数据等。
    - 注意事项：List 是不可变的，所有操作都会返回一个新的列表。如果需要可变集合，可以使用 ListBuffer 或 ArrayBuffer。
"""

# Python 列表和Java的 数组不一样，Java数组本身是没有类型，我们所说的数组类型指的是数组当中的元素的类型，
# 这从它的定义就看看到：Java中的数组是内存当中固定长度的同类型元素的有序集合。
# Python不是，列表是内存中一个有序的可变集合，可以包含不同类型的元素。

# 如下创建一个list，里面的元素有很多种不同的类型

my_list = [1, 2.093, 3 * 3, '''我是汉字''', "Hello Python", ['yu', 'jia', 'hao'], {'name': 'yujiahao'}]

print(my_list)


print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')


# todo 1.1、 基础使用


lst = ['宇佳豪', '周润发', '张学友', '王祖贤', '林青霞', '李嘉欣']

# 直接打印的话，长啥样子就打印出什么样子

print(f'直接打印：{lst}')

# 列表和字符串一样也拥有索引，也是根据访问顺序不同索引起始值不同

# 访问第一个和最后一个
print(lst[0])
print(lst[-1])

# 修改元素
lst[1] = '周星驰'
print(f'修改元素：{lst}')

# 增加元素
lst.append('张曼玉')
print(f'增加：{lst}')

# 指定索引插入元素
lst.insert(2, ['吴孟达', '张国荣'])
print(f'指定索引插入元素：{lst}')

# 删除元素
lst.remove('张曼玉')
print(f'删除元素：{lst}')

# 删除并返回最后一个元素
popped_person = lst.pop()
print(f'删除并返回最后一个元素：{popped_person}')
print(lst)

# 按照角标删除元素
del lst[0]
print(f'按照角标删除元素：{lst}')

# 列表反转
lst.reverse()
print(f'列表反转：{lst}')

# 和字符串一样，列表也使用切片规则，这里不多说
print(f'列表切片：{lst[:3]}')

# 列表方法,这里注意字符串单双引号的问题不要相同的嵌套，混搭一下
# 打印列表中 '王祖贤' 的出现次数
print(f'打印次数：{lst.count("王祖贤")}')

# 获取元素的索引
index_of_lin = lst.index('林青霞')
print(f'获取元素的索引：{index_of_lin}')

# 清空列表
lst.clear()
print(lst)

print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

# todo 1.2、列表排序

'''对于列表排序，我们上面的例子是不能直接排序的，因此这里要注意一些问题'''

# 1. 数据类型一致性

"""
确保列表中的所有元素都是可以互相比较的类型。
例如，不能同时包含字符串和数字，因为 Python 无法直接比较它们。
"""

# 正确的例子：所有元素都是整数
lst = [3, 1, 4, 2]
lst.sort()
print(lst)

# 错误的例子：包含字符串和整数，会抛出 TypeError
lst = [3, 'apple', 1]
try:
    lst.sort()
except TypeError as e:
    print(e)  # 输出: '<' not supported between instances of 'str' and 'int'

# 2. 原地排序 vs 返回新列表

"""
list.sort() 方法会原地排序列表并返回 None。
如果需要保留原列表，可以使用 sorted() 函数，它会返回一个新的排序后的列表。
"""

lst = [3, 1, 4, 2]

# 使用 sorted() 返回一个新的排序后的列表
sorted_lst = sorted(lst)
print(sorted_lst)  # 输出: [1, 2, 3, 4]
print(lst)  # 输出: [3, 1, 4, 2]（原列表未改变）

# 使用 list.sort() 原地排序
lst.sort()
print(lst)  # 输出: [1, 2, 3, 4]


# 3. 自定义排序

"""
可以通过 key 参数指定自定义的排序规则，例如按字符串长度排序。
"""

lst = ['apple', 'banana', 'cherry', 'date']

# 按字符串长度排序
lst.sort(key=len)
print(lst)  # 输出: ['date', 'apple', 'banana', 'cherry']


# 4. 逆序排序

"""
可以通过 reverse 参数进行逆序排序。
"""

lst = [3, 1, 4, 2]

# 逆序排序
lst.sort(reverse=True)
print(lst)  # 输出: [4, 3, 2, 1]


# 5. 处理嵌套列表
"""
如果列表中包含嵌套列表或其他不可比较的元素，需要先处理这些元素。
那我们上面的就可以先移除嵌套列表，然后对剩下的字符串排序
"""

lst = [3, [1, 2], 4, 2]

# 移除嵌套列表
lst = [item for item in lst if not isinstance(item, list)]
lst.sort()
print(lst)  # 输出: [2, 3, 4]


# 6. 稳定排序
"""
Python 的排序算法是稳定的，这意味着在排序过程中，
相等的元素保持它们在原列表中的相对顺序。
这个列表的元素是一个tuple2，并且里面有两个元素的第二个元素相同，因此最终结果是在整体排序之后，相同的数值按照原列表中的先后顺序依次排序
（其中lambda x:x[1] 是匿名函数的写法，这个后面会说，这里先知道就行）
"""

lst = [('apple', 2), ('banana', 1), ('cherry', 2)]

# 按第二个元素排序
lst.sort(key=lambda x: x[1])
print(lst)  # 输出: [('banana', 1), ('apple', 2), ('cherry', 2)]


# 7. 多级排序
"""
可以通过多个 key 参数实现多级排序。
"""

lst = [('apple', 2), ('banana', 1), ('cherry', 2), ('date', 1)]

# 先按第二个元素排序，再按第一个元素排序
lst.sort(key=lambda x: (x[1], x[0]))
print(lst)  # 输出: [('banana', 1), ('date', 1), ('apple', 2), ('cherry', 2)]

print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

#  todo 1.3、列表推导式

'''列表推导式（List Comprehensions）是 Python 中的一种简洁而强大的创建列表的方式。
它允许你使用一种简明的语法来生成新的列表，这种语法通常比传统的循环和 append() 方法更简洁、更易读。

列表推导式的基本语法如下：
    [expression for item in iterable if condition]
    expression 是生成列表元素的表达式。-说白了就是最终的元素,你想怎么处理就怎么处理
    item 是从 iterable 中依次取出的元素。--没啥意思
    iterable 是任何可迭代对象，如列表、元组、字符串等。--必须是可迭代的对象
    condition 是一个可选的过滤条件，只有满足条件的元素才会被包含在生成的列表中。--就是对前面迭代出来的进行筛选

'''

# 先对比一下普通创建和使用列表推导式创建的方式

# 普通创建：先声明一个空列表，然后使用循环往里面追加数据
a_lst = []
for i in range(10):
    a_lst.append(i)
print(a_lst)

# 使用列表推导式就简单的多

b_lst = [i for i in range(10)]
print(b_lst)

# 当然可以增加一些条件，比如创建10以内的所有偶数的平方根

c_lst = [i ** 2 for i in range(10) if i % 2 == 0]
print(c_lst)

# 可以生成一个元素为tuple的列表，这个列表是一个包含 1 到 3 和 'a' 到 'c' 的笛卡尔积。

dkr = [(i, j) for i in range(1, 4) for j in 'abc']

print(dkr)


# 看一个例子

'''
经典的水仙花数（阿姆斯特朗数），分别用两种方式获取 
【水仙花数】是指一个三位数，其各位数字的立方和等于该数本身。
例如，153 是一个水仙花数，因为 1^3+5^3+3^3=153
解题思路：
①获取所有三位数
②将三位数打散，得到三个独立的数字
③套用公式，符合输出
'''

# 传统方法流程控制语句

armstrong_numbers = []
for i in range(100, 1000):
    flatmap = [int(j) for j in str(i)]
    print(flatmap)
    if sum(k ** 3 for k in flatmap) == i:
        armstrong_numbers.append(i)
print(f'1000以内的水仙花数为：{armstrong_numbers}')

# 纯列表推导式,就是改写一下(就跟Java中三元表达式那种，把if-else 改写成一行了，但是两个不是一回事儿啊)

armstrong_numbers = [i for i in range(100, 1000) if sum(int(j) ** 3 for j in str(i)) == i]
print(armstrong_numbers)

lst = ['宇佳豪', '周润发', '张学友', '王祖贤', '林青霞', '李嘉欣']

# 在列表中过滤包含"张"的名字
zhang_names = [name for name in lst if '张' in name]
print(zhang_names)  # ['张学友']

print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')




# todo 1.4、列表复制（深拷贝、浅拷贝）

'''
【浅拷贝】：只引用地址，不引用实际对象。

    怎么理解呢，就说浅拷贝是指创建一个新的对象，这个新对象是原始对象的一个副本，
    但它只复制了原始对象中的引用，而不是引用的实际对象。换句话说，浅拷贝只复制了对象的第一层内容，
    对于嵌套的对象（如列表中的列表），浅拷贝只复制了外层对象的引用，内层对象仍然是共享的。
    在Python中，copy 方法和切片方法（[:]）都可以用来复制列表，都是创建列表的浅拷贝但它们在某些方面有细微的区别。
    
    区别总结
        -语法：
            copy 方法是列表对象的内置方法，语法为 lst.copy()。
            切片方法使用切片语法 [:]。
        -性能：
            在大多数情况下，切片方法 [:] 和 copy 方法在性能上差异不大，都能高效地创建浅拷贝。
        -可读性：
            有些开发者认为 lst.copy() 更具可读性，因为它明确表示了复制操作。
        
        
【深拷贝】可以使用 Python 的 copy 模块中的 deepcopy 方法。深拷贝会递归地复制对象及其所有嵌套对象，生成一个完全独立的副本。
        这样，修改副本的任何部分都不会影响到原始对象。    

深拷贝与浅拷贝的区别
    浅拷贝：
        复制对象本身，但不递归复制嵌套的对象。
        修改嵌套对象会影响到原对象和副本。
    深拷贝：
    
        递归复制对象及其所有嵌套对象，生成完全独立的副本。
        修改副本不会影响原对象。
深拷贝的应用场景
    需要对复杂对象结构进行独立操作，而不希望影响原始对象。
    需要确保副本与原始对象完全独立，避免任何共享数据。
深拷贝的注意事项
        性能：深拷贝会递归地复制对象及其所有嵌套对象，这可能会带来一定的性能开销，特别是对于深度嵌套的对象结构。
        循环引用：如果对象中存在循环引用，deepcopy 方法能够正确处理，但需要注意可能的性能影响。
'''

# ① 浅拷贝举例

# 使用 copy 方法
lst1 = ['宇佳豪', '王祖贤']
lst2 = lst1.copy()  # 使用 copy 方法

print(f'原始列表：{lst1}')
print(f'复制后的列表：{lst2}')

# 修改 lst1
lst1.append('张学友')

print(f'修改后的原始列表：{lst1}')
print(f'修改后的复制列表：{lst2}')

# 使用切片方法
lst1 = ['宇佳豪', '王祖贤']
lst2 = lst1[:]

print(f'原始列表：{lst1}')
print(f'复制后的列表：{lst2}')

# 修改 lst1
lst1.append('张学友')

print(f'修改后的原始列表：{lst1}')
print(f'修改后的复制列表：{lst2}')

# ②深拷贝举例

# 要导入copy模块

import copy

# 创建一个包含嵌套列表的列表
original_list = [[1, 2, 3], [4, 5, 6]]

# 使用 deepcopy 方法创建深拷贝
deep_copied_list = copy.deepcopy(original_list)

print(f'原始列表：{original_list}')
print(f'深拷贝后的列表：{deep_copied_list}')

# 修改原始列表中的嵌套列表
original_list[0][0] = 'a'

print(f'修改后的原始列表：{original_list}')
print(f'修改后的深拷贝列表：{deep_copied_list}')

print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

# todo 1.5、列表的嵌套
# 注意:采用降维操作,一层一层的看就好

lst = [1, '太白', 'wusir', ['麻花疼', ['可口可乐'], '王健林']]

# 找到wusir
print(lst[2])

# 找到太白和wusir
print(lst[1:3])

# 找到太白的白字
print(lst[1][1])

# 将wusir拿到,然后首字母大写 在扔回去

s = lst[2]
s = s.capitalize()
lst[2] = s
print(lst)

# 简写
lst[2] = lst[2].capitalize()
print(lst)

# 把太白换成太黑
lst[1] = lst[1].replace('白', '黑')

# 把麻花疼换成麻花不疼
lst[3][0] = lst[3][0].replace('疼', '不疼')
print(lst)

# 在可口可乐后边添加一个雪碧
lst[3][1].append('雪碧')
print(lst)

print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

# 歇会做个练习

'''
写代码，有如下列表，按照要求实现每一个功能。
lis = [2, 3, "k", ["qwe", 20, ["k1", ["tt", 3, "1"]], 89], "ab", "adv"]
将列表lis中的"tt"变成大写（用两种方式）。
将列表中的数字3变成字符串"100"（用两种方式）。
将列表中的字符串"1"变成数字101（用两种方式）。
'''

lis = [2, 3, "k", ["qwe", 20, ["k1", ["tt", 3, "1"]], 89], "ab", "adv"]

# 1. 将列表 lis 中的 "tt" 变成大写

# 方法一：直接索引修改

lis[3][2][1][0] = lis[3][2][1][0].upper()
print(lis)

# 方法二：用 index 方法查找并修改

sublist = lis[3][2][1]
index_tt = sublist.index("tt")
sublist[index_tt] = sublist[index_tt].upper()
print(lis)

# 2. 将列表中的数字 3 变成字符串 "100"

# 方法一：直接索引修改

lis[1] = "100"
lis[3][2][1][1] = "100"
print(lis)

# 方法二：用 index 方法查找并修改

# 修改第一个 3
index_3 = lis.index(3)
lis[index_3] = "100"

# 修改嵌套列表中的 3
sublist = lis[3][2][1]
index_3_nested = sublist.index(3)
sublist[index_3_nested] = "100"

print(lis)

# 3. 将列表中的字符串 "1" 变成数字 101

# 方法一：直接索引修改

lis[3][2][1][2] = 101
print(lis)

# 方法二：用 index 方法查找并修改

sublist = lis[3][2][1]
index_1 = sublist.index("1")
sublist[index_1] = 101
print(lis)

print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

# todo 1.6、更多列表操作

# 合并列表
'''
在Python中，使用 + 操作符和 extend 方法来合并列表会有不同的行为。
使用 + 操作符会创建一个新的列表，它包含两个列表的所有元素。原始列表 lst1 和 lst2 不会被修改。
使用 extend 方法将第二个列表的所有元素添加到第一个列表中。这个操作是就地进行的（in-place），即它会修改原始列表 lst1，并且 extend 方法本身返回 None。
'''

# 使用 + 操作符或 extend() 方法来合并两个列表：

lst1 = ['宇佳豪', '王祖贤']
lst2 = ['张学友', '李嘉欣']

add_list = lst1 + lst2

print(f'使用 + 合并列表：{add_list}')

extend_list = lst1.extend(lst2)
print(f'使用 extend 合并列表本身会返回None：{extend_list}')
print(f'使用 extend 合并列表要看修改后的列表才可以：{lst1}')

# 查找最大值和最小值
# 如果列表中包含可比较的元素，可以使用 max() 和 min() 函数：

numbers = [10, 20, 30, 40, 50]

# 查找最大值
max_value = max(numbers)
print(max_value)  # 50

# 查找最小值
min_value = min(numbers)
print(min_value)  # 10

# 计算列表元素的和
# 使用 sum() 函数计算列表中所有数值的和：

numbers = [10, 20, 30, 40, 50]

# 计算和
total = sum(numbers)
print(total)  # 150

# 使用 enumerate() 函数
# enumerate() 函数用于在遍历列表时获取元素的索引和值：

lst = ['宇佳豪', '周润发', '张学友', '王祖贤']

for index, name in enumerate(lst):
    print(f"Index: {index}, Name: {name}")



# 使用 zip() 函数 就是拉链，Scala也有
# zip() 函数用于并行遍历多个列表：

lst1 = ['宇佳豪', '周润发']
lst2 = ['演员', '歌手']

for name, profession in zip(lst1, lst2):
    print(f"{name} 是 {profession}")

# 使用 filter() 函数
# filter() 函数用于筛选列表中的元素：

lst = ['宇佳豪', '周润发', '张学友', '王祖贤']

# 筛选名字长度大于3的元素
filtered_lst = list(filter(lambda x: len(x) > 3, lst))
print(filtered_lst)  # ['宇佳豪', '周润发', '张学友', '王祖贤']

# 使用 map() 函数
# map() 函数用于对列表中的每个元素应用指定的函数：

lst = ['YuJiahao', 'zhOurunfa', 'zhangXueYou', 'WangZuxian']

# 将所有名字转换为大写
uppercase_lst = list(map(lambda x: x.upper(), lst))
print(uppercase_lst)


# 列表的拆包

# 基本拆包

# 基本拆包
my_list = [1, 2, 3]
a, b, c = my_list
print(a, b, c)  # 输出: 1 2 3

# 拆包并忽略某些元素
# 你可以使用占位符（通常是 _）来忽略不需要的元素。

# 拆包并忽略某些元素
my_list = [1, 2, 3, 4, 5]
a, b, _ = my_list[:3]
print(a, b)  # 输出: 1 2

# 使用星号 * 进行扩展拆包
# 星号 * 允许你将剩余的元素存储在一个列表中。

# 使用星号进行扩展拆包
my_list = [1, 2, 3, 4, 5]
a, *b, c = my_list
print(a)  # 输出: 1
print(b)  # 输出: [2, 3, 4]
print(c)  # 输出: 5

# 拆包嵌套列表
# 如果列表中包含嵌套列表，你也可以进行嵌套拆包。

# 拆包嵌套列表
my_list = [1, [2, 3], 4]
a, (b, c), d = my_list
print(a)  # 输出: 1
print(b)  # 输出: 2
print(c)  # 输出: 3
print(d)  # 输出: 4


# 与函数参数结合使用
# 拆包操作可以与函数参数结合使用，传递多个参数。

def my_function(a, b, c):
    print(a, b, c)

# 使用列表进行拆包传参
'''星号 * 和双星号 ** 操作符用于函数参数的拆包。具体来说，单星号 * 用于拆包列表或元组，而双星号 ** 用于拆包字典'''
my_list = [1, 2, 3]
my_function(*my_list)  # 输出: 1 2 3

# 在循环中拆包
# 拆包也可以在循环中使用，特别是在处理包含元组或列表的列表时。

# 在循环中拆包
list_of_tuples = [(1, 2), (3, 4), (5, 6)]
for a, b in list_of_tuples:
    print(a, b)
# 输出:
# 1 2
# 3 4
# 5 6


# 拆包字典中的键值对
# 你还可以拆包字典中的键值对。

# 拆包字典中的键值对
my_dict = {'a': 1, 'b': 2, 'c': 3}
for key, value in my_dict.items():
    print(key, value)
# 输出:
# a 1
# b 2
# c 3